Read more of this story at Slashdot.
Read more of this story at Slashdot.
Last time, we speculated on how the buggy control panel extension truncated a value that it had right in front of it. When we sent our analysis to the vendor, they wrote back, “Can you check the driver version numbers on these crashes?”
When we checked the driver version numbers on all the crashing systems, they were something like “build 314”, when the current driver build number is something like “build 2718”. These users are running drivers that are ridiculously old! The vendor fixed that bug ages ago, but the user hasn’t gotten the fix. What’s going on?
My theory was that these users have turned off Windows Update or are otherwise declining to upgrade their video drivers. But I learned that my theory was probably wrong.
The deal here is that these are video drivers, which are a category of drivers where computer manufacturers have a lot of control. The manufacturer certifies the drivers for use on their PCs after performing their own acceptance testing on their specific hardware configurations. (Which are probably not hardware configurations that the video card vendors themselves are aware of.)
This responsibility carries forward post-sale. The computer manufacturer remains responsible for certifying driver updates, presumably by testing them against reference PCs that they maintain in their labs. Sometimes, manufacturers get customized versions of the video cards (all the better to differentiate your product with, my dear), which is why the video card vendor “driver downloads” sites often warn you to check with your computer manufacturer before installing a driver.
In practice, computer manufacturers are diligent about certifying drivers for a year, year and a half, two years tops.¹ After that, it’s not uncommon for them to abandon that model and not bother certifying drivers for it any more. All customers with that model of PC are just stuck with whatever video drivers were current as of the time the manufacturer stopped certifying drivers.
Microsoft maintains generic drivers for many classes of hardware, but intentionally sets them as low priority so that the PC manufacturer-provided drivers take precedence. The video drivers received directly from video card manufacturers are similarly deprioritized by the video card vendors. The computer manufacturer-certified drivers take precedence, even if that certification is horribly out of date.
¹ I wouldn’t be surprised if the length of time they certify drivers is somehow correlated with the length of the computer warranty.
The post Why has the display control panel pointer truncation bug gone unfixed for so long? appeared first on The Old New Thing.
Kalshi is a high-tech prediction market that allows people to "forecast the future" (their term). It is about contracts and information, the company says, making its offerings more like a soybean futures contract than a round of blackjack or a pull on the one-armed bandit.
Still, prediction markets look a lot like betting if you squint, which is why states like New York have tried to regulate them under gambling laws. To head this off, Kalshi has sought federal protection under the Commodity Futures Trading Commission (CFTC). Yes, this means regulation for Kalshi, but it also means the CFTC will sue states like Kentucky, Minnesota, Illinois, and Rhode Island, trying to pre-empt their laws in favor of a single national standard that the CFTC controls.
While this battle plays out, government insiders continue to generate insider trading stories after using their work knowledge to place bets "forecast the future" and make huge sums of money. The classic example, of course, was Gannon Ken Van Dyke, a US soldier who participated in planning the capture of Venezuela's Nicolas Maduro and then made $410,000 from that knowledge on the prediction site Polymarket. Van Dyke was arrested in April.
But there are also more ridiculous stories, such as disgraced former Congressman George Santos, who allegedly talked up his upcoming appearance at the State of the Union, secretly bet on whether he would attend, and then didn't go at the last minute to score a payout.
This activity raises questions, like: How many people are gambling forecasting the future based on government secrets or insider knowledge? How many are actively manipulating results they have bet on? Even the Trump White House was concerned enough to issue a memo in March telling employees not to "use nonpublic information to buy or sell these contracts."
But concerns have lingered, especially after major wins on contracts involving US government policy or actions. Such suspicions will not be helped by new allegations today from multiple outlets that insider trading on Kalshi has extended even to President Trump's teleprompter operator, who allegedly made $100,000 "forecasting" specific words and phrases that might appear in Trump speeches.
According to sources speaking to NPR, Trump aide Gabriel Perez bet on something called a "mention market." This is a section of Kalshi where you can sink money into contracts on crucial questions such as "What will Domino's say during their next earnings call?" (Currently, $26,000 has been invested in this question; the smart money thinks that "Parmesan" and "DomOS" are more likely to be mentioned than not.)
Kalshi contracts include words that Domino's might say.
In the case of Perez, his "forecasting" allegedly took place over several months at the end of last year and the beginning of this year, and his contracts were sometimes adjusted in the middle of Trump speeches. According to ABC:
Sources say Perez typically has the final eyes on nearly all of the president's prepared remarks—and is often known to take last-minute edits from Trump himself... In certain instances, investigators uncovered times when Perez would back out of certain bets mid-speech when Trump skipped over a portion of the speech that included a word he had previously bet would be mentioned, the sources said.
This conjures up an amazing mental image: The teleprompter operator for one of the world's most powerful people tapping away at his phone during a Trump speech to ensure he made more money for himself.
In this case, Kalshi (which bans this sort of activity) flagged unusual activity, investigated, and found the customer was a federal employee. It then froze his funds and sent the information to the CFTC, which is said to have investigated and to be in settlement talks with Perez.
Will Perez actually be prosecuted? Apparently not; as ABC notes, "sources said the CFTC alerted federal prosecutors in Manhattan, who declined to open a criminal investigation."
The White House did say today that Perez “will no longer be working at the White House.”
Whatever you want to call it, "predicting the future with money at stake" has become huge business in America. A recent (and terrific) long article by McKay Coppins in The Atlantic showed people what a year of online sports gambling looks like, and it raised serious questions about the negative issues that widespread, legal, bet-from-your-phone gambling might cause in a country where "roughly half of men ages 18 to 49 have an active account with an online sportsbook."
Prediction markets, which have invested heavily in advertising during the World Cup, are only going to make these challenges more acute as they extend "forecasting" from sports to drug trials, flight cancellations, and the specific words that people will say in speeches.
Last time, we found that a crash in a control panel extension was caused by pointer truncation. The code had a perfectly good 64-bit pointer in its hand, but somehow lost its mind and opted to throw away the top 32 bits.
How could something like this happen?
My guess is that this code started out as perfectly good 32-bit code:
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON); SetWindowLong(hwndButton, GWL_WNDPROC, (LONG)g_originalWndProc);
And then they recompiled it as 64-bit code and got an error.
error C2065: 'GWL_WNDPROC': undeclared identifier
They then went back to the documentation and saw that for 64-bit Windows, GWL_WNDPROC was renamed to GWLP_WNDPROC.
So they fixed it by changing GWL_WNDPROC to GWLP_WNDPROC.
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, GWL_WNDPROC, (LONG)g_originalWndProc);
However, the point of renaming the value was not to annoy you. The point of renaming the value was to call your attention to places where pointer truncation is likely to occur. In this case, it’s the final parameter, the original 64-bit window procedure. The build break is telling you that you are probably passing a 32-bit value as something that should be 64-bit. In this case, because it was being cast to (LONG). You are expected to upgrade the GWL_WNDPROC to GWLP_WNDPROC and at the same time upgrade the cast from (LONG) to (LONG_PTR).
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON); SetWindowLong(hwndButton, GWL_WNDPROC, (LONG_PTR)g_originalWndProc);
Now, this was likely an oversight rather than a systemic failure, because they did manage to subclass the window properly:
WNDPROC g_originalWndProc; HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON); g_originalWndProc = (WNDPROC)SetWindowLong(hwndButton, GWLP_WNDPROC, (LONG_PTR)subclassWndProc);
They merely missed a spot. Perhaps the developer got distracted after fixing the symbol name and forgot to come back and fix the pointer.
Next time, we’ll look at why this bug has remained unfixed for so long.
The post Speculating on how the buggy control panel extension truncated a value that it had right in front of it appeared first on The Old New Thing.

The number one crash in the display control panel looks like this:
rax=ffffffffc836d280 rbx=0000000000000001 rcx=0000000000030440 rdx=0000000000000002 rsi=0000000000030440 rdi=0000000080006011 rip=00007ffac835cd1e rsp=000000155e48e3f8 rbp=000000155e48e749 r8=0000000000000000 r9=0000000000000000 r10=007fffffffe41b69 r11=00007df502390000 r12=0000000000000000 r13=0000000000000000 r14=0000000000000002 r15=0000000000000000 iopl=0 nv up ei pl nz na pe nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 ntdll!LdrpDispatchUserCallTarget+0xe: 00007fff`924acd1e mov r11,qword ptr [r11+r10*8] ds:04007df5`0159db48=???????????????? 0:000> k Call Site ntdll!LdrpDispatchUserCallTarget+0xe user32!UserCallWinProcCheckWow+0x2bd user32!DispatchClientMessage+0x9c user32!__fnDWORD+0x33 ntdll!KiUserCallbackDispatcherContinue win32u!ZwUserDestroyWindow+0x14 comctl32!_RealPropertySheet+0x36d comctl32!_PropertySheet+0x47 Display!PropertySheetW+0x5d Display!AdvancedSettingSheetHelper+0x3be Display!ShowAdapterSettings+0x89 rundll32!CallRunDllFunction+0x1c rundll32!wWinMain+0x2bf rundll32!__wmainCRTStartup+0x1c9 kernel32!BaseThreadInitThunk+0x14 ntdll!RtlUserThreadStart+0x21
From the stack, we see that we have a display adapter settings property sheet. We are destroying it, and we crash trying to validate the window procedure address.
We saw some time ago that you can pull out the bad address by inspection.
0:000> u .-e . ntdll!LdrpDispatchUserCallTarget: 00007fff`924acd10 mov r11,qword ptr [ntdll+0x001813a8] 00007fff`924acd17 mov r10,rax 00007fff`924acd1a shr r10,9 00007fff`924acd1e mov r11,qword ptr [r11+r10*8]
The register that is the source of the shift is rax, so that’s the function pointer. And from the register dump, we see that the address is
rax=ffffffffc836d280
Yeah, that address doesn’t look like a valid function pointer.
On 64-bit systems, user-mode pointers have low addresses (which start with 0000), and kernel-mode pointers have high addresses (which start with ffff). So this function pointer is clearly invalid for user mode.
Maybe we can fix it so it’s valid again. Let’s see what code addresses are valid in this process.
0:000> lm start end module name 00000001`80000000 00000001`80043000 contoso 00007ff6`44570000 00007ff6`44587000 rundll32 00007fff`6a4f0000 00007fff`6a6b7000 d3d9 00007fff`6e600000 00007fff`6e6a9000 comctl32_7fff6e600000 00007fff`6f5d0000 00007fff`6f5e5000 pcacli 00007fff`753b0000 00007fff`753c1000 sfc_os ... 00007fff`91020000 00007fff`910f0000 comdlg32 00007fff`912b0000 00007fff`915e6000 combase 00007fff`91600000 00007fff`91794000 user32 00007fff`917a0000 00007fff`91852000 kernel32 00007fff`918e0000 00007fff`91989000 SHCore 00007fff`91990000 00007fff`91ae6000 ole32 00007fff`91af0000 00007fff`91b16000 gdi32 00007fff`91b20000 00007fff`91bc3000 advapi32 00007fff`91bd0000 00007fff`91c67000 sechost 00007fff`91c70000 00007fff`91cc2000 shlwapi 00007fff`91cd0000 00007fff`91ced000 imagehlp 00007fff`91d50000 00007fff`921c0000 setupapi 00007fff`92220000 00007fff`92355000 msctf 00007fff`92420000 00007fff`92610000 ntdll ...
Ny suspicion is that the function pointer got truncated to a 32-bit value, and then was sign-extended back up to a 64-bit value. So we are looking for valid function pointers of the form xxxxxxxx`924bbde0. In the above list of valid code addresses, the only ones that have the lower bits in the 92xxxxxx range all have a high 32 bits of 00007fff, so let’s plug that in and see if we get a window procedure.
0:000> ln 7fff924bbde0 (00007fff`924bbde0) ntdll!NtdllButtonWndProc_A | (00007fff`924bbdf0) ntdll!NtdllButtonWndProc_W
Jackpot.
So the caller probably subclassed a window, and then tried to restore the original window procedure, but messed up and restored only the bottom 32 bits.
But who could that be?
0:000> k Call Site ntdll!LdrpICallHandler+0xf ntdll!RtlpExecuteHandlerForException+0xf ntdll!RtlDispatchException+0x219 ntdll!KiUserExceptionDispatch+0x2e ntdll!LdrpDispatchUserCallTarget+0xe user32!UserCallWinProcCheckWow+0x2bd user32!DispatchClientMessage+0x9c user32!__fnDWORD+0x33 ntdll!KiUserCallbackDispatcherContinue win32u!ZwUserDestroyWindow+0x14 comctl32!_RealPropertySheet+0x36d comctl32!_PropertySheet+0x47 Display!PropertySheetW+0x5d Display!AdvancedSettingSheetHelper+0x3be Display!ShowAdapterSettings+0x89 rundll32!CallRunDllFunction+0x1c rundll32!wWinMain+0x2bf rundll32!__wmainCRTStartup+0x1c9 kernel32!BaseThreadInitThunk+0x14 ntdll!RtlUserThreadStart+0x21
This is a property sheet, so we should be able to extract the pages of the property sheet. (Note: Requires internal Microsoft symbols, so you won’t be able to do this at home.)
0:000> .frame d
09 00000017`85a7e820 00007fff`86e60349 Display!AdvancedSettingSheetHelper+0x3be
0:000> dv
hwndParent = <value unavailable>
psh = struct _PROPSHEETHEADERW_V2
szMonitor = wchar_t [140] "Generic PnP Monitor"
rPages = struct _PSP *[100]
iResult = 0n0
The desktop background control panel is extensible, and the way that a plug-in adds a page to the desktop background control panel is by handling the IShellPropSheetExt::AddPages method and calling the provided “page adding function” with a HPROPSHEETPAGE. What that function does is add the HPROPSHEETPAGE to the pages in the property sheet. (We can see that there’s room for 100 of them in the rPages.)
And the psh is the PROPSHEETHEADER.
0:000> ?? psh
struct _PROPSHEETHEADERW_V2
+0x000 dwSize : 0x60
+0x004 dwFlags : 0x2000001
+0x008 hwndParent : 0x00000000`000401aa HWND__
+0x010 hInstance : 0x00007fff`86e50000 HINSTANCE__
+0x018 hIcon : (null)
+0x020 pszCaption : 0x00000017`85a7f100 "Generic PnP Monitor and Contoso Chipset"
+0x028 nPages : 4
+0x030 nStartPage : 0
+0x038 ppsp : 0x00000017`85a7ec70 _PROPSHEETPAGEW
+0x038 phpage : 0x00000017`85a7ec70 -> 0x000001d5`4e1aac90 _PSP
We see that there are four pages, so we can inspect the first four HPROPSHEETPAGEs in rPages.
And hey look, we have an array of HPROPSHEETPAGE structures
0:000> ?? psh.phpage[0] struct _PSP * 0x000001d5`4e1aac90 0:000> ?? psh.phpage[1] struct _PSP * 0x000001d5`4e19e470 0:000> ?? psh.phpage[2] struct _PSP * 0x000001d5`4e19e520 0:000> ?? psh.phpage[3] struct _PSP * 0x000001d5`4e1d26d0
The HPROPSHEETPAGE is an opaque structure, but we can dump it and look for interesting things, for entertainment purposes only.
0:000> dps 0x000001d5`4e1aac90 l4 000001d5`4e1aac90 000001d5`4e1aac60 000001d5`4e1aac98 00000000`00000000 000001d5`4e1aaca0 00004088`00000068 000001d5`4e1aaca8 00007fff`88d70000 deskadp 0:000> dps 0x000001d5`4e19e470 l4 000001d5`4e19e470 000001d5`4e19e440 000001d5`4e19e478 00000000`00000000 000001d5`4e19e480 00004088`00000068 000001d5`4e19e488 00007fff`893e0000 deskmon 0:000> dps 0x000001d5`4e19e520 l4 000001d5`4e19e520 000001d5`4e19e4f0 000001d5`4e19e528 00000000`00000000 000001d5`4e19e530 000040c8`00000068 000001d5`4e19e538 00007fff`86e30000 colorui 0:000> dps 0x000001d5`4e1d26d0 l4 000001d5`4e1d26d0 000001d5`4e1bcb30 000001d5`4e1d26d8 000001d5`4e1d26a0 000001d5`4e1d26e0 0000008a`00000068 000001d5`4e1d26e8 00000001`80000000 contoso
There are a bunch of HMODULEs here, which are probably the modules that the property sheet page came from. The first three come with Windows. The last one apparently is Contoso. Let’s focus on at last one.
After the first two values (which look like pointers), we have 0x00000068 which is not-coincidentally sizeof(PROPSHEETPAGE), so I’m going to guess that this is where the system stores the PROPSHEETPAGE that the handle was created from.
Note: Note that this is an implementation detail and should be used only for debugging purposes. Please don’t write programs that rely on this, because it can change.¹
0:000> dt comctl32!_PROPSHEETPAGEW 000001d5`4e1d26e0
+0x000 dwSize : 0x68
+0x004 dwFlags : 0x8a
+0x008 hInstance : 0x00000001`80000000 HINSTANCE__
+0x010 pszTemplate : 0x00000000`00000589 "--- memory read error at address 0x00000000`00000589 ---"
+0x010 pResource : 0x00000000`00000589 DLGTEMPLATE
+0x018 hIcon : 0x00000000`000503b9 HICON__
+0x018 pszIcon : 0x00000000`000503b9 "--- memory read error at address 0x00000000`000503b9 ---"
+0x020 pszTitle : 0x000001d5`4e19cde0 "?????"
+0x028 pfnDlgProc : 0x00000001`800047ac contoso+0x47ac
+0x030 lParam : 0n2015682301296
+0x038 pfnCallback : (null)
+0x040 pcRefParent : (null)
+0x048 pszHeaderTitle : (null)
+0x050 pszHeaderSubTitle : (null)
+0x058 hActCtx : (null)
+0x060 hbmHeader : (null)
+0x060 pszbmHeader : (null)
The dialog procedure is 0x00000001`800047ac. I’m hoping I can reverse-engineer it enough to see the place where it subclassed the button incorrectly.
00000001`800047ac mov [rsp+8],rbx
00000001`800047b1 mov [rsp+10h],rbp
00000001`800047b6 mov [rsp+18h],rsi
00000001`800047bb push rdi
00000001`800047bc sub rsp,30h
00000001`800047c0 mov rdi,r9 ; rdi = r9 = lParam
00000001`800047c3 mov rbp,r8 ; rbp = r8 = wParam
00000001`800047c6 mov esi,edx ; esi = edx = message
00000001`800047c8 mov rbx,rcx ; rbx = rcx = hdlg
00000001`800047cb cmp edx,110h ; Q: WM_INITDIALOG?
00000001`800047d1 jne 00000001`800047e2 ; N: Skip
00000001`800047d3 mov r8,[r9+30h] ; Y: r8 = ((PROPSHEETPAGE*)r9)->lParam
00000001`800047d7 mov edx,0FFFFFFEBh ; edx = -21
; ecx = hdlg (unchanged)
00000001`800047dc call [00000001`8002b4a0] ; mystery function 1
00000001`800047e2 mov edx,0FFFFFFEBh ; edx = -21
00000001`800047e7 mov rcx,rbx ; rcx = hdlg
00000001`800047ea call [00000001`8002b480] ; mystery function 2
00000001`800047f0 test rax,rax ; Q: Failed?
00000001`800047f3 je 00000001`8000480b ; Y: Bail out
00000001`800047f5 mov r9,rbp ; param4 = wParam
00000001`800047f8 mov r8d,esi ; param3 = message
00000001`800047fb mov rdx,rbx ; param2 = hdlg
00000001`800047fe mov rcx,rax ; param1 = from mystery function 2
00000001`80004801 mov [rsp+20h],rdi ; param5 = lParam
00000001`80004806 call 00000001`800045fc ; mystery function 3
00000001`8000480b mov rbx,[rsp+40h] ; restore registers
00000001`80004810 mov rbp,[rsp+48h]
00000001`80004815 mov rsi,[rsp+50h]
00000001`8000481a add rsp,30h
00000001`8000481e pop rdi
00000001`8000481f ret ; done
We know that the lParam parameter to the WM_INITDIALOG message is the value passed as the “parameter” to functions like CreateDialogParam, and specifically for property sheets, it’s a pointer to a PROPSHEETPAGE. And we saw from the structure dump above that offset 0x30 is the lParam.
From the structure of this function, it’s clear that the magic value -21 is GWLP_USERDATA, mystery function 1 is SetWindowLongPtr, and mystery function 2 is GetWindowLongPtr. This is a standard pattern for dialog box functions, and it’s common to use a wrapper function.
The real dialog procedure is the third mystery function, so let’s look at that.
00000001`800045fc mov [rsp+8],rbx 00000001`80004601 mov [rsp+10h],rbp 00000001`80004606 mov [rsp+18h],rsi 00000001`8000460b push rdi 00000001`8000460c push r12 00000001`8000460e push r13 00000001`80004610 sub rsp,20h 00000001`80004614 mov rsi,[rsp+60h] ; rsi = lParam 00000001`80004619 mov rbp,r9 ; rbp = wParam 00000001`8000461c mov ebx,r8d ; ebx = message 00000001`8000461f mov r13,rdx ; r13 = hdlg 00000001`80004622 mov rdi,rcx ; rdi = this 00000001`80004625 cmp r8d,2Bh ; Q: WM_DRAWITEM? 00000001`80004629 jne 00000001`80004685 ; N: Skip
After the initial register spilling and saving, it checks if the message is 0x2B: WM_DRAWITEM. That’s not particularly interesting to us, so let’s assume it’s not.
00000001`80004685 sub ebx,2 ; Q: WM_DESTROY? 00000001`80004688 je 00000001`8000470f
Ooh, the WM_DESTROY message is interesting. It’s probably going to restore the original window procedure in its WM_DESTROY handler, and that’s where we hope to find the truncation.
00000001`8000470f mov rcx,[rdi+110h] ; rcx = something 00000001`80004716 movsxd rbx,dword ptr [00000001`80039c50] ; rbx = something 00000001`8000471d mov edx,668h ; ecx = some number 00000001`80004722 call [00000001`8002b4e0] ; mystery function 4 00000001`80004728 mov r8,rbx ; r8 = something 00000001`8000472b mov edx,0FFFFFFFCh ; edx = -12 00000001`80004730 mov rcx,rax ; rcx = function 4 retval 00000001`80004733 call [00000001`8002b4a0] ; mystery function 1 again
On receipt of the WM_DESTROY message, the code starts by getting something out of the this pointer (which we saw in the prologue was saved in rdi), and loads some other thing from a global variable.
Next, it calls mystery function 00000001`8002b4e0 with 0x668 as the second parameter. Not sure what that is, but we’ll keep it in mind.
Next, we set up for another function call, and this one we recognize: 00000001`8002b4a0 is the import address table entry for SetWindowLongPtr. We saw it in the static dialog procedure.
The parameters are the window handle that was obtained from mystery function 4, the constant -12, and the 32-bit value we loaded from 00000001`80039c50. The mystery function 4 was probably GetDlgItem. And since we figured out that the function being called is SetWindowLongPtr, the value -12 is GWLP_WNDPROC.
The value being set is the third parameter, which was loaded by movsxd dword ptr, which is a 32-bit to 64-bit sign-extended load. This is a problem because the window procedure is a 64-bit value.
I bet they loaded the value incorrectly.
0:000> dp 00000001`80039c50 l1 00000001`80039c50 00007fff`924bbde0
Hey look, it’s the full 64-bit pointer we were supposed to have used, except we messed up and truncated the pointer.
The C++ source code probably looked like this:
SetWindowLongPtr(GetDlgItem(m_hdlg, 0x668),
GWLP_WNDPROC, (LONG)g_originalWndProc);
The cast to LONG is what’s doing the truncation and sign extension. It should be a cast to LONG_PTR.
We can patch this into the binary after looking at the processor instruction encoding documentation.
The original instruction was
00000001`80004716 48631d33550300 movsxd rbx,dword ptr [00000001`80039c50]
The documentation says that the encoding for movxsd r64, r/m32 is “REX.W + 63 /r”.
What we want is mov rbx, [00000001`80039c50], and the documentation says that the encoding for mov r64, r/m64 is “REX.W + 8B /r”.
So let’s patch the 63 to 8b.
0:000> eb 00000001`80004717 8b 0:000> u 00000001`80004716 l1 00000001`80004716 488b1d33550300 mov rbx,qword ptr [00000001`80039c50]
This is literally a one-byte bug fix.
Next time, we’ll speculate on how this bug arose.
Bonus reading: The decoy control panel.
¹ Back in the late 1990’s, we discovered a program that reverse-engineered the internal data structures of the Windows 95 property sheet manager to the point where instead of passing an HPROPSHEETPAGE that was created by the CreatePropertySheetPage function, it created fake HPROPSHEETPAGEs that it had constructed manually in memory. This made adding support for Unicode property sheets that much harder because the internal structure of HPROPSHEETPAGEs changed in order to support both ANSI and Unicode property sheet pages, and they were passing the old version. The property sheet manager has to recognize that it is being given a fake HPROPSHEETPAGE and convert it on the fly to a real one.
The post The case of the invalid function pointer when shutting down the display control panel appeared first on The Old New Thing.